﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;

namespace SimpleIR
{
	/// <summary>
	/// This class is use to represent all branch operations that may appear in the bytecode stream.
	/// </summary>
	public sealed class IRBranchOp : IRByteCode
	{
		/// <summary>
		/// An array containing all the acceptable opcodes for this operation class (in order)
		/// </summary>
		private static readonly OpCode.Op[] AcceptableOps = { OpCode.Op.BranchU, OpCode.Op.BranchC, OpCode.Op.Switch, OpCode.Op.Throw, OpCode.Op.Return };

		/// <summary>
		/// Possible targets for the branch.
		/// </summary>
		private readonly uint[] m_targetlist;

		private IRBranchOp(OpCode.Op opc, uint[] trgts, IRSourceLocation sloc)
			: base(opc, sloc)
		{
			Debug.Assert(AcceptableOps.Contains(opc));
			
			this.m_targetlist = trgts;
		}

		public static IRBranchOp MakeBranchUOp(uint target, IRSourceLocation sloc)
		{ return new IRBranchOp(OpCode.Op.BranchU, new uint[] { target }, sloc); }

		public static IRBranchOp MakeBranchCOp(uint iftrue, uint iffalse, IRSourceLocation sloc)
		{ return new IRBranchOp(OpCode.Op.BranchC, new uint[] { iftrue, iffalse }, sloc); }

		public static IRBranchOp MakeReturnOp(IRSourceLocation sloc)
		{ return new IRBranchOp(OpCode.Op.Return, new uint[] { }, sloc); }

		public static IRBranchOp MakeSwitchOp(uint[] switchTargets, IRSourceLocation sloc)
		{ return new IRBranchOp(OpCode.Op.Switch, switchTargets, sloc); }

		public static IRBranchOp MakeThrowOp(IRSourceLocation sloc)
		{ return new IRBranchOp(OpCode.Op.Throw, new uint[] { }, sloc); }

		public override String ToString()
		{
			if(this.OperationCode == OpCode.Op.BranchU)
				return "bru:     " + this.m_targetlist[0].ToString();
			else if(this.OperationCode == OpCode.Op.BranchC)
				return "brcond:  " + this.m_targetlist[0].ToString() + " : " + this.m_targetlist[1].ToString();
			else if(this.OperationCode == OpCode.Op.Return)
				return "ret:     ";
			else if(this.OperationCode == OpCode.Op.Switch)
				return "switch:  ";
			else if(this.OperationCode == OpCode.Op.Throw)
				return "throw:   ";
			else
				return "--BAD OPCODE--";
		}

		/////////
		//Accessors for later use

		public override void GetBranchTargets(List<uint> tl) 
		{
			if(this.m_targetlist != null)
				tl.AddRange(this.m_targetlist);
		}

		/// <summary>
		/// The unique target for a BranchU operation.
		/// </summary>
		public uint BRUTarget
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.BranchU);
				return this.m_targetlist[0];
			}
		}

		/// <summary>
		/// The target to jump to if we have a BranchC operation and the stack value is true (1).
		/// </summary>
		public uint BRCondTrueTarget
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.BranchC);
				return this.m_targetlist[0];
			}
		}

		/// <summary>
		/// The target to jump to if we have a BranchC operation and the stack value is false (0).
		/// </summary>
		public uint BRCondFalseTarget
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.BranchC);
				return this.m_targetlist[1];
			}
		}

		/// <summary>
		/// The switch target array.
		/// </summary>
		public uint[] SwitchTargets
		{
			get
			{
				Debug.Assert(this.OperationCode == OpCode.Op.Switch);
				return this.m_targetlist;
			}
		}
	}
}
